package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.apis.article.IArticleClient;
import com.heima.apis.schedule.IScheduleClient;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.constants.ScheduleConstants;
import com.heima.common.constants.WemediaConstants;
import com.heima.common.exception.CustomException;
import com.heima.common.tess4j.Tess4jClient;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.common.enums.TaskTypeEnum;
import com.heima.model.schedule.dtos.Task;
import com.heima.model.wemedia.dtos.WmNewsContentDto;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.ProtostuffUtil;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.mapper.WmChannelMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmSensitiveMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmNewsAutoScanService;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.TesseractException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author brianxia
 * @version 1.0
 * @date 2022-10-22 14:40
 */
@Service
@Slf4j
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {

    @Autowired
    private GreenTextScan greenTextScan;
    @Autowired
    private GreenImageScan greenImageScan;
    @Autowired
    private WmNewsMapper wmNewsMapper;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private IArticleClient articleClient;
    @Autowired
    private WmUserMapper wmUserMapper;
    @Autowired
    private WmChannelMapper wmChannelMapper;
    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;
    @Autowired
    private Tess4jClient tess4jClient;
    @Autowired
    private IScheduleClient scheduleClient;

    class ContentData{
        private String text;
        private List<byte[]> images;

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public List<byte[]> getImages() {
            return images;
        }

        public void setImages(List<byte[]> images) {
            this.images = images;
        }
    }

    //提取文本和图片信息
    private ContentData getContentData(WmNews wmNews) {

        ContentData contentData = new ContentData();

        String content = wmNews.getContent();
        StringBuilder sb = new StringBuilder();
        HashSet<String> urls = new HashSet<>();

        List<WmNewsContentDto> wmNewsContentDtos = JSONArray.parseArray(content, WmNewsContentDto.class);
        for (WmNewsContentDto dto : wmNewsContentDtos) {
            if(Objects.equals(dto.getType(), WemediaConstants.WM_NEWS_TYPE_IMAGE)){
                urls.add(dto.getValue());
            }else if (Objects.equals(dto.getType(), WemediaConstants.WM_NEWS_TYPE_TEXT)){
                sb.append(dto.getValue());
                sb.append("-");
            }
        }

        //把标题加入
        sb.append(wmNews.getTitle());

        //封面中的图片也审核  a.jpg,b.jpg,c.jpg
        String images = wmNews.getImages();
        if(StringUtils.isNotBlank(images)){
            String[] split = images.split(",");
            urls.addAll(Arrays.asList(split));
        }
        //获取图片内容+提取图片内容中文字
        List<byte[]> bytes = new ArrayList<>();
        for (String url : urls) {
            byte[] bytes1 = fileStorageService.downLoadFile(url);
            try{
                BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(bytes1));
                String s = tess4jClient.doOCR(bufferedImage);
                sb.append("-");
                sb.append(s);
            }catch (Exception e){
                log.error("下载图片出错",e);
                throw new CustomException(AppHttpCodeEnum.SERVER_ERROR,"下载图片出错");
            }

            bytes.add(bytes1);
        }

        contentData.setText(sb.toString());

        contentData.setImages(bytes);

        return contentData;
    }

    //初始化执行一次 guava cache内存缓存技术，定期刷新，一天刷新一次，在凌晨刷新
    //明天带大家优化一下 实现每天定期刷新
    @PostConstruct
    public void init(){
        List<String> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery()
                .select(WmSensitive::getSensitives)
        ).stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
        SensitiveWordUtil.initMap(wmSensitives);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Async
    public void autoScan(WmNews wmNews) {
        //判断当前文章待审核状态
        if(!Objects.equals(wmNews.getStatus(),WmNews.Status.SUBMIT.getCode())){
            return;
        }

        ContentData contentData = getContentData(wmNews);

        //敏感词审核
        String text = contentData.getText();
        Map<String, Integer> result = SensitiveWordUtil.matchWords(text);
        if(!result.isEmpty()){
            saveWmNewsStatus(wmNews, WmNews.Status.FAIL
                    ,"内容包含敏感词");
            return;
        }

        //文本审核
        boolean b = autoScanText(contentData.getText(), wmNews);
        if(!b){
            //审核失败，直接结束
            return;
        }

        //图片审核
        b = autoScanImage(contentData.getImages(), wmNews);
        if(!b){
            //审核失败，直接结束
            return;
        }

        saveWmNewsStatus(wmNews,WmNews.Status.SUCCESS,"审核成功，待发布");

        //审核通过，任务添加到延迟队列
        Task task = new Task();
        task.setTaskType(TaskTypeEnum.NEWS_SCAN_TIME.getTaskType());
        task.setPriority(TaskTypeEnum.NEWS_SCAN_TIME.getPriority());
        task.setExecuteTime(wmNews.getPublishTime().getTime());
        task.setParameters(ProtostuffUtil.serialize(wmNews));

//        byte[] bytes = JSON.toJSONString(wmNews).getBytes(StandardCharsets.UTF_8);
//
//        WmNews wmNews1 = JSON.parseObject(new String(bytes), WmNews.class);

        scheduleClient.addTask(task);
    }

    @Override
    public void publish(WmNews wmNews) {
        //审核成功
        //通过远程接口保存数据
        ResponseResult responseResult = saveAppArticle(wmNews);

        //校验返回结果是否正确
        if(responseResult == null || !Objects.equals(responseResult.getCode(),AppHttpCodeEnum.SUCCESS.getCode())){
            //远程接口处理失败
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR,"调用远程接口失败");
        }
        Long articleId = (Long) responseResult.getData();

        //执行本地保存
        wmNews.setArticleId(articleId);
        saveWmNewsStatus(wmNews, WmNews.Status.PUBLISHED,"审核通过");
    }


    private ResponseResult saveAppArticle(WmNews wmNews) {
        //wmNews 转换成参数对象
        ArticleDto articleDto = new ArticleDto();
        BeanUtils.copyProperties(wmNews,articleDto);

        Long articleId = wmNews.getArticleId();
        if(articleId != null){
            articleDto.setId(articleId);
        }

        articleDto.setAuthorId(Long.valueOf(wmNews.getUserId()));
        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
        if(wmUser == null){
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST
                    ,"用户不存在"+wmNews.getUserId());
        }
        articleDto.setAuthorName(wmUser.getName());

        WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());
        if(wmChannel == null){
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST
                    ,"频道不存在"+wmNews.getChannelId());
        }
        articleDto.setChannelName(wmChannel.getName());

        articleDto.setLayout(wmNews.getType());
        articleDto.setCreatedTime(new Date());

        ResponseResult responseResult = articleClient.saveArticle(articleDto);
        return responseResult;
    }

    //审核文本代码
    private boolean autoScanText(String text, WmNews wmNews) {

        try {
            Map map = greenTextScan.greeTextScan(text);

            String suggestion = (String) map.get("suggestion");

            if(Objects.equals(suggestion,"pass")){
                //1.成功
                return true;
            }else if(Objects.equals(suggestion,"block")){
                //2.失败 2
                saveWmNewsStatus(wmNews, WmNews.Status.FAIL,"文章内容包含敏感词，请重新确认!");
                return false;
            }else if(Objects.equals(suggestion,"review")){
                //3.转人工 3
                saveWmNewsStatus(wmNews, WmNews.Status.ADMIN_AUTH,"文章内容可能包含敏感词，需要人工处理!");
                return false;
            }

        } catch (Exception e) {
            log.error("调用阿里云审核接口失败",e);
        }

        saveWmNewsStatus(wmNews, WmNews.Status.ADMIN_AUTH,"文章内容可能包含敏感词，需要人工处理!");
        return false;
    }

    //审核文本代码
    private boolean autoScanImage(List<byte[]> images, WmNews wmNews) {

        if(CollectionUtils.isEmpty(images)){
            //没有数据 代表不需要审核
            return true;
        }

        try {

            Map map = greenImageScan.imageScan(images);

            String suggestion = (String) map.get("suggestion");

            if(Objects.equals(suggestion,"pass")){
                //1.成功
                return true;
            }else if(Objects.equals(suggestion,"block")){
                //2.失败 2
                saveWmNewsStatus(wmNews, WmNews.Status.FAIL,"文章图片包含敏感词，请重新确认!");
                return false;
            }else if(Objects.equals(suggestion,"review")){
                //3.转人工 3
                saveWmNewsStatus(wmNews, WmNews.Status.ADMIN_AUTH,"文章图片可能包含敏感词，需要人工处理!");
                return false;
            }

        } catch (Exception e) {
            log.error("调用阿里云审核接口失败",e);
        }

        saveWmNewsStatus(wmNews, WmNews.Status.ADMIN_AUTH,"文章图片可能包含敏感词，需要人工处理!");
        return false;
    }

    private void saveWmNewsStatus(WmNews wmNews, WmNews.Status fail,String reason) {
        wmNews.setStatus(fail.getCode());
        wmNews.setReason(reason);
        wmNewsMapper.updateById(wmNews);
    }

    @Scheduled(cron = "* * * * * ?")
    public void consum(){
        ResponseResult responseResult = scheduleClient.poll(TaskTypeEnum.NEWS_SCAN_TIME.getTaskType()
                , TaskTypeEnum.NEWS_SCAN_TIME.getPriority());

        if(responseResult != null && Objects.equals(responseResult.getCode()
                ,AppHttpCodeEnum.SUCCESS.getCode())){
            Object data = responseResult.getData();
            String s = JSON.toJSONString(data);
            Task task = JSON.parseObject(s, Task.class);

            if(task.getParameters() != null && task.getParameters().length > 0){
                byte[] parameters = task.getParameters();
                //反序列化，还原成对象
                WmNews wmNews = ProtostuffUtil.deserialize(parameters, WmNews.class);
                //发布文章
                publish(wmNews);
            }

        }

    }


    public static void main(String[] args) {
        ApArticle apArticle = new ApArticle();
        apArticle.setId(1L);

        String s = JSON.toJSONString(ResponseResult.okResult(apArticle));

        ResponseResult<ApArticle> responseResult = JSON.parseObject(s, new TypeReference<ResponseResult<ApArticle>>
                (ResponseResult.class){});
//        ResponseResult responseResult = JSON.parseObject(s, ResponseResult.class);
//        //泛型类型已经被擦除
//        Object data1 = responseResult.getData();
//        String s1 = JSON.toJSONString(data1);
//        ApArticle apArticle1 = JSON.parseObject(s1, ApArticle.class);
        //类型先转换成字符串，再进行还原
        ApArticle data = responseResult.getData();
        System.out.println(data);
    }
}
